package com.niiwoo.civet.user.service.local.sms;

import com.niiwoo.civet.user.constants.LoginAccountErrorCode;
import com.niiwoo.civet.user.dao.entity.SMSRecord;
import com.niiwoo.civet.user.dto.request.RefreshSMSCodeRequestDTO;
import com.niiwoo.civet.user.service.local.LoginAccountRedisService;
import com.niiwoo.tripod.provider.exception.BizException;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

public abstract class LimitedSMSSender extends AbstractSMSSender {

    private static final Logger logger = LoggerFactory.getLogger(AbstractSMSSender.class);

    @Override
    protected void beforeSend(RefreshSMSCodeRequestDTO requestDTO, SMSConfigItem configItem) {
        innerBeforeSend(requestDTO);
        Object[] formatParams = new Object[]{this.getType().getValue(), requestDTO.getLoginMobileNo()};
        // 验证码是否过期
        if (userSMSLocalService.getCacheValue(LoginAccountRedisService.SMS_CODE_FORMAT, formatParams).isPresent()) {
            throw new BizException(LoginAccountErrorCode.CAN_NOT_SEND_SMS_AGAIN);
        }
        Optional<SMSConfigItem> smsConfigItemOptional = Optional.ofNullable(configItem);
        // 当日限制校验
        int dayTimesThreshold = smsConfigItemOptional.map(SMSConfigItem::getDayTimesThreshold).orElse(Math.max(this.getType().getDayThreshold(), 0));
        logger.info("LimitedSMSSender beforeSend dayTimesThreshold={}, timesCache={}", dayTimesThreshold, getTimesCache(LoginAccountRedisService.SMS_CODE_DAY_TIMES_FORMAT, formatParams));
        if (dayTimesThreshold > 0 && getTimesCache(LoginAccountRedisService.SMS_CODE_DAY_TIMES_FORMAT, formatParams) >= dayTimesThreshold) {
            throw new BizException(this.getType().getDayThresholdErrorCode());
        }
        // 当月限制校验
        int monthTimesThreshold = smsConfigItemOptional.map(SMSConfigItem::getMonthTimesThreshold).orElse(Math.max(this.getType().getMonthThreshold(), 0));
        if (monthTimesThreshold > 0 && getTimesCache(LoginAccountRedisService.SMS_CODE_MONTH_TIMES_FORMAT, formatParams) >= monthTimesThreshold) {
            throw new BizException(this.getType().getMonthThresholdErrorCode());
        }
    }

    private int getTimesCache(String formatter, Object[] formatParams) {
        return userSMSLocalService.getCacheValue(formatter, formatParams).map(Integer::valueOf).orElse(0);
    }

    abstract protected void innerBeforeSend(RefreshSMSCodeRequestDTO requestDTO);

    @Override
    protected String getContent(RefreshSMSCodeRequestDTO requestDTO, SMSRecord record) {
        return String.format(innerContentTemplate(), record.getAuthCode());
    }

    abstract protected String innerContentTemplate();

    @Override
    protected void afterSendSuccess(RefreshSMSCodeRequestDTO requestDTO, SMSRecord record, SMSConfigItem configItem) {
        Object[] formatParams = new Object[]{this.getType().getValue(), requestDTO.getLoginMobileNo()};
        Optional<SMSConfigItem> smsConfigItemOptional = Optional.ofNullable(configItem);
        // 缓存验证码
        userSMSLocalService.setCacheValue(LoginAccountRedisService.SMS_CODE_FORMAT, formatParams, record.getAuthCode(), (long) Optional.ofNullable(configItem).map(SMSConfigItem::getDuration).orElse(this.getType().getCodeExpireTime()), TimeUnit.SECONDS);
        // 缓存当天发送次数
        int dayTimesThreshold = smsConfigItemOptional.map(SMSConfigItem::getDayTimesThreshold).orElse(Math.max(this.getType().getDayThreshold(), 0));
        if (dayTimesThreshold > 0) {
            userSMSLocalService.incrCacheValue(LoginAccountRedisService.SMS_CODE_DAY_TIMES_FORMAT, formatParams, getLeftSecondsToday(), TimeUnit.SECONDS);
        }
        // 缓存当月发送次数
        int monthTimesThreshold = smsConfigItemOptional.map(SMSConfigItem::getMonthTimesThreshold).orElse(Math.max(this.getType().getMonthThreshold(), 0));
        if (monthTimesThreshold > 0) {
            userSMSLocalService.incrCacheValue(LoginAccountRedisService.SMS_CODE_MONTH_TIMES_FORMAT, formatParams, getLeftSecondsThisMonth(), TimeUnit.SECONDS);
        }
    }

    @Override
    public void removeAuthCodeCache(String mobileNo) {
        userSMSLocalService.delCacheValue(LoginAccountRedisService.SMS_CODE_FORMAT, new Object[]{this.getType().getValue(), mobileNo});
    }

    @Override
    public boolean isAuthCodeCacheRight(String mobileNo, String exceptCode) {
        if (StringUtils.isBlank(exceptCode)) {
            return false;
        }
        Object[] formatParams = new Object[]{this.getType().getValue(), mobileNo};
        Optional<String> codeOptional = userSMSLocalService.getCacheValue(LoginAccountRedisService.SMS_CODE_FORMAT, formatParams);
        boolean match = codeOptional.isPresent() && codeOptional.get().equals(exceptCode);
        if (match) {
            this.removeAuthCodeCache(mobileNo);
        }
        return match;
    }

    @Override
    public void removeDayTimesCache(String mobileNo) {
        userSMSLocalService.delCacheValue(LoginAccountRedisService.SMS_CODE_DAY_TIMES_FORMAT, new Object[]{this.getType().getValue(), mobileNo});
    }

    @Override
    public void removeMonthTimesCache(String mobileNo) {
        userSMSLocalService.delCacheValue(LoginAccountRedisService.SMS_CODE_MONTH_TIMES_FORMAT, new Object[]{this.getType().getValue(), mobileNo});
    }

    /**
     * 获取当天剩余秒数
     */
    private long getLeftSecondsToday() {
        LocalDate now = new LocalDate();
        return new Duration(now.toDateTimeAtCurrentTime(), now.plusDays(1).toDateTimeAtStartOfDay()).getStandardSeconds();
    }

    /**
     * 获取当月剩余秒数
     */
    private long getLeftSecondsThisMonth() {
        LocalDate now = new LocalDate();
        return new Duration(now.toDateTimeAtCurrentTime(), now.plusMonths(1).withDayOfMonth(1).toDateTimeAtStartOfDay()).getStandardSeconds();
    }

}
